# Copyright 2001-2007 by iNeed,Inc. All Rights Reserved.
#-*- encoding:utf-8 -*-
"""
use cases:

"""
import sys, os, time, string,common,message
import logging,traceback,sock_asyncore
from common import *

__author__  = "Number Huang<huang.bafeng@iNeed.com>"
__status__  = "developing"
__version__ = "0.1109.13.1"

class StandbyTask(Task):
    """
    Standby Task is to handle some request/response in an async way, which may costs a long time,
    say, wait for the GUI end user to confirm/cancel.
    The task will wait until GUI user responds or timeout
    """

    def __init__(self,owner,session,params,name,timeout=60,timeIntervalBySec=0.1,reporter=None):


        self.paramDict                      = getServiceByDict(params)
        self.paramList                      = getServiceByList(params)
        self.mandtoryFields                 = getMandotoryField(params)
        self.owner                           = owner
        self.guiUser                         = owner.owner
        self.session                         = session
        self.timeout                         = timeout
        self.firstTime                       = time.time()
        self.number                          = 0   #run number
        loopCount                            = timeout*1/timeIntervalBySec
        self.actionName                      = name
        self.pubID                           = self.paramDict["pubID"]
        self.log                             = owner.log
        self.reporter                        = reporter
        
        self.curService = self.owner.queryService(self.pubID)
        #if self.curService:
        
        self.extraInfo                       = {CSTR2_TIMEOUT:self.timeout,CSTR2_PUBID:self.pubID}
        super(StandbyTask,self).__init__(session,"%s_%d"%(self.actionName,self.pubID),timeIntervalBySec,loopCount)

    def run(self):
        """
        The task run will only executes at the first time.

        """
        if self.number==0:
           self.number += 1
           
           status  = notifyGUITable[self.actionName]
           self.curService.updateStatus(status)
           self.callStandby()
        """
        #remove the timeout check to the UIPorxy,like HTMLPeer
        elif self.lastTime - self.firstTime > self.timeout:
           self._handleResponse(EN_TIMEOUT,retCodeMap[EN_TIMEOUT])
        """
        #self.log.debug("run %d"%self.loopCount)
    def callStandby(self):
         """
         The subclass must overwride it, otherwise, it is not necessary to add a new subclass
         """
         info = ""
         for param in self.paramList:
                info += param+"\r\n"
         serviceDict  = self.paramDict
         poolingGroup = serviceDict.get(CSTR3_POOLINGGROUP)
         if poolingGroup:
            for serviceDict,serviceList in poolingGroup:
               for param in serviceList:
                   info += param+"\r\n"
         reporter = Reporter(self.reporter,self._handleResponse,self.timeout)
   
         self.owner.waitForGUIConfirm(self.actionName, self.curService.orderDict,self.extraInfo,reporter)
         
         
    def _handleResponse(self,retCode,retInfo,extraInfo=None,exit=True):
        """
        In most of case, the reporter will call the method eventually.
        TOGD event is an exception, because its task is to handle another dialog.
        """
        self.session.sendStandyResponse("NOTIFY",self.mandtoryFields, retCode,retInfo)
        if exit:
            self.exit()
class PoolingProcessor(object):  
    """
    manage the pooling info, may do further like schedule the optimized pooling
    """
    def __init__(self,owner,mainID):
        self.poolingList             = []
        self.owner = owner
        self.log = owner.log
        self.activeOrder = None
        self.mainID = mainID
        
    def _setActiveOrder(self,order):
        self.activeOrder = order
    
    def getActiveOrder(self):
        return self.activeOrder#self.poolingList.get(self.activeOrderID)
    
    def update(self):
        """
        update other pooling orders
        """
        self._calculateNextDest() 
            
    def addPooling(self,order):
        if order not in self.poolingList:
            self.poolingList.append(order)
            if not self.activeOrder:
                self._setActiveOrder(order)
            else:#update status
                if self.activeOrder.isExecuting():
                    order.updateStatus(ESE_GOFOR_PICKUP)
                self.update()    
                
    def deletePooling(self,order):
        if order in self.poolingList:
            self.poolingList.remove(order)  
            self.update() 
            
    def getRemainSeats(self):
         num = 0
         for order in self.poolingList:
            num += order.orderDict["number"]
         return configall.MAXPERSON - num     
    
    def getLastService(self):
         destTime = 0
         lastOrder = None
         for order in self.poolingList:
            if order.orderDict["destinationTime"]>destTime:
                destTime = order.orderDict["destinationTime"]
                lastOrder = order
         return lastOrder 
    
    def getFirstService(self,curTime):
         departTime = curTime + 0x7fff#impossible time
         firstOrder = None
         for order in self.poolingList:
            if order.orderDict[EDS_DEPARTTIME]>curTime and order.orderDict[EDS_DEPARTTIME]<departTime:
                departTime = order.orderDict[EDS_DEPARTTIME]
                firstOrder = order
         return firstOrder  
            
    def _calculateNextDest(self):
        """
        calculate next destination
        TODO: currently, we only use the distance of 2d vector to decide the next dest
        it needs an actual algorithm eventually
        """
        minDist=[0x7fffffff,None]
        curLocation = self.owner.getLocation()[:2]
        for order in self.poolingList:
            dist1 = calculate2DLength(curLocation,order.getNextDest())
            if minDist[0]>dist1:
                minDist=[dist1,order]
        
        self._setActiveOrder(minDist[1])
    
    def getNextDest(self):
        
        if self.activeOrder:
            return self.activeOrder.getNextDest()
        else:
            return None
    
    def isMainOrder(self,id):
        return id==self.mainID
    
#because the dict is not ordered,so create one serviceParam for easy publish
#in python2.7, there is a new dict
orderedParams=("departureLocation","departureTime","timeout","destinationLocation",
               "destinationTime","priceRange","type","number"
               )
class TaxiService(object):
    """
        stores all the info of one servcice, and offers a serviceParam to faciliate the operation of string
    """

    def __init__(self,owner,paramsDict,status=ESE_WFR_PUBLISH):
        """
        The timeout is seconds after publish, say,publish it in 11:00,the timeout=900 means
        the last avaiable time of the service will be 11:15. After that, the center will give up the service, which in client side, should
        be marked as closed
        TODO:
            there are some reduntant info.
            servicePrams is for publish
            orderDict is for query final order

        """
        self.owner = owner
        self.log   = owner.log
        self.session = None
       
       
        """
        self.serviceParam = []
        for item in orderedParams:
            if paramsDict.has_key(item):
                self.serviceParam.append({item:paramsDict[item]})
        """
      
        self.cseq                    = 0

        self.pubID                   = 0

        self.status                  = status
        self.orderDict               = paramsDict
        self.published               = False
        self.poolingProcessor        = None
    def updateStatus(self,newStatus):
         if newStatus!=self.status:
         
                
            self.log.debug("%d:status->(%s->%s)"%(self.pubID,FSMTable[self.status][2],FSMTable[newStatus][2]))
            """
            if newStatus==ESS_NONE or newStatus==ESS_CLOSED:#ESS_CLOSED:
                self.owner.updateStatus(ES_IDLE)
            
            else:
                self.owner.updateStatus(ES_BUSY)
                self.owner.reportStatus()
            if newStatus==ESS_CLOSED:
            """    
            self.status = newStatus
    
    def addComment(self,value):
        self.comment=value
    
    def getComment(self):
        return self.comment
    
    def getStatusText2(self):#@get chinese text
        return FSMTable[self.status][3] 
   
    def getDepartText2(self):
        if self.orderDict.has_key("address"):
            return getUStr(self.orderDict["address"])
        else:
            return getUStr(self.orderDict["departureLocation"][2])
    
    def getDepartTimeText2(self):
       
        return translateTimeC(self.orderDict["departureTime"])
      
    def getDestText2(self):
        if self.orderDict.has_key("address2"):
            return getUStr(self.orderDict["address2"])
        else:
            return getUStr(self.orderDict["destinationLocation"][2])#.encode(CHINESE_CODING)
    
    def readyExecute(self):
        return self.status==ESE_ORDER_DONE and self.owner.role==EPR_DRIVER
    
    def isWaitingGUIResponse(self):  
        return self.status>=ESE_WF_GUI_CO and self.status<=ESE_WF_GUI_ET
    
    def isExecuting(self):
        return self.status>=ESE_GOFOR_PICKUP and self.status<ESE_TRANSATION_DONE
    
    def getNextDest(self):
        if self.status==ESE_TRANSATION_GOING:
            return (self.orderDict['x2'],self.orderDict['y2'])
        else:#if self.status==ESE_ORDER_DONE:
            return (self.orderDict['x'],self.orderDict['y'])
        
def randomPort(phoneNum):
    #get the last 4 number
    last4 = int(re.search(r".+(\d\d\d\d)$",phoneNum).group(1))
    return last4 + 30000


       
class Peer(object):
    """
    The proxy between GUI and the session
    The baseclass of passenger and driver
    """

    def __init__(self,owner):
        self.owner = owner
        self.engineFactory = TaskEngineFactory(self)
        self.taskEngine = self.engineFactory.getOneEngine("taskEngine%d"%self.role,True)
        self.status = ES_UNLOGGED


        self.msgEngine  = self.engineFactory.getOneEngine("msgEngine%d"%self.role)#msgEngine should be used independetly to avoid the block
        self.msgProxy   = message.MsgProxy(self)
        self.msgEngine.addTask(self.msgProxy)

        self.ipAddr = ["127.0.0.1",2011]#fake address
    
        self.serviceList=[]
        
        """
        self.responseMap={
        "LOGIN":self.handleLogResponse,
        "LOGOUT":self.handleLogOutResponse,
        "PUBLISH":self.handlePublishResponse,
        "UNPUBLISH":self.handleUnPublishResponse,
        "NOTIFY":self.handleNotifyResponse,
        }
        """
        
        self.requestMap={
        "NOTIFY":self.handleNotifyRequest,
        }

        self.notifyRequestMap={
                               CSTR_GPS:self._handleGPSRequest
                               }
        
        self.notifyResponseMap={
                              
                                }
        
        self.activeService = None
        
    def exit(self):
        self.logOut()
        self.engineFactory.exit()
        self.msgProxy.exit()

    def updateStatus(self,newStatus):
        self.status = newStatus
        
    def reportStatus(self,extraParams=None):
        pass#driver will overwride it and passenger do nothing so far
    
        
    def waitForGUIConfirm(self,method,infoDict,extraInfoDict,reporter):
        """
        Transfer the request to event, and post to UI
        """
        statusReporter = Reporter(reporter,self._handleGUIConfirm,extraParam={"pubID":extraInfoDict["pubID"]})
        self.owner.handleRequest(method,infoDict,extraInfoDict,statusReporter)
        
    def _handleGUIConfirm(self,retCode,retInfo,extraInfo):
        """
        response from GUI to UIPeer
        """
       
        service = self.queryService(extraInfo[CSTR2_PUBID])
       
        if retCode==EN_OK:
            service.updateStatus(FSMTable[service.status][0])
        else:
            service.updateStatus(FSMTable[service.status][1])
       
    
    """
    Method
    """
    def logOut(self,reporter=None):
        if self.status != ES_UNLOGGED:
            reporter2 = Reporter(reporter,self.handleLogOutResponse)
            self._sendRequest(self.mainSession,"LOGOUT",self.mainFrom2Head,reporter2)
            self.updateStatus(ES_UNLOGGED)

    def logIn(self,phoneNum,pwd,centerID,serverAddr,reporter=None):

        if self.status == ES_UNLOGGED:
            self.phoneNum = phoneNum
            self.pwd      = pwd
            self.centerID = centerID
            self.mainFrom2Head = "From: %s\r\n"%self.phoneNum + "To: %s\r\n"%self.centerID
            if self.role==EPR_DRIVER:#currently, only drivers need listener
                if sock_asyncore.IsUseBufSock():
                    self.ipAddr[0] = self.phoneNum
                    port = int(self.phoneNum)
                else:
                    port = randomPort(phoneNum)
                self.ipAddr[1] = port
                self.listner    = self.msgProxy.createListner(tuple(self.ipAddr))
            params = self.mainFrom2Head + "Role: %d\r\n"%self.role + "Authorization: port=%d,pwd=%s\r\n"%(self.ipAddr[1],pwd)
            self.mainSession    = self.msgProxy.createClientSession(serverAddr)
            self.mainSession.peer.setPhone(centerID)
            reporter2 = Reporter(reporter,self.handleLogResponse)
            self._sendRequest(self.mainSession,"LOGIN",params,reporter2)


    def _publish(self,service,reporter):

        params = self.mainFrom2Head + "Service: taxi\r\n"
        for item in translateOrderList:
            if service.orderDict.has_key(item[0]):
                params+="%s: %s\r\n"%(item[0],item[2](service.orderDict[item[0]]))
       
        service.cseq =  self.mainSession.getNextCSeq()
        reporter2 = Reporter(reporter,self.handlePublishResponse)
        service.updateStatus(ESE_WFR_PUBLISH)
        self._sendRequest(self.mainSession,"PUBLISH",params,reporter2)
        


    def _unPublish(self,service,reporter):

        params = self.mainFrom2Head + "PubID: %d\r\n"%service.pubID + "departureTime: %s\r\n"%getLocalTime(service.orderDict["departureTime"])
        reporter2 = Reporter(reporter,self.handleUnPublishResponse)
        self._sendRequest(self.mainSession,"UNPUBLISH",params,reporter2)

    
    
    """
    GPS
    """


    def getPeerPos(self,peerNum,reporter1=None):

        session = self.msgProxy.getSessionByPhone(peerNum)
        params = "From: %s\r\n"%self.phoneNum + "To: %s\r\n"%peerNum
        params += "Action: %s\r\n"%CSTR_GPS + "pubID: %d\r\n"%session.pubID
        reporter = Reporter(reporter1)
        self._sendRequest(session,"NOTIFY",params,reporter)

    def handleGPSResponse(self,retCode,retInfo,extraInfo=None):
        pass #because the info will pass by parent report,so just place it here for future use

    def _handleGPSRequest(self,session,params):
        """
        this request also plays the role of INVITE function to create the connection between 2 peers
        """
        serviceDict     = getServiceByDict(params)
        pubID = serviceDict["pubID"]
        service = self.queryService(pubID)
        if not service.session:
            service.session = session
            session.setPubID(pubID)
            locationParams = getLocationDict(params)
            session.peer.setPhone(locationParams["From"])
        session.sendStandyResponse("NOTIFY",params[3], EN_OK,retCodeMap[EN_OK])

    """
    send/recv msg
    """
    def _waitforSyncResponse(self,session,request,cseq,reporter):

            retCode,retInfo,extraInfo = session.pollResponse(cseq)
            timeout = reporter.timeout*10
            while (not retCode) and timeout:
                time.sleep(0.1)
                timeout -= 1
                retCode,retInfo,extraInfo = session.pollResponse(cseq)
            if timeout==0:
                raise Exception, "timeout to sync the response of %s,cseq=%d"%(request,cseq)
            else:
                reporter.notify(retCode,retInfo,extraInfo)



    def _sendRequest(self,session,method,params,reporter,flag=0):

        if not session.connected:
            self.status = ES_UNLOGGED
            raise sock_asyncore.EDisConnected()
        location = self.getLocation()
        #currently, uer sync mode but keep the async feature
        if reporter and (not reporter.async):
           cseq = session.sendRequest(method,params,None,location,flag)
           self._waitforSyncResponse(session,method,cseq,reporter)
        else:
           return session.sendRequest(method,params,reporter,location,flag)



    """
    handle ret code from response
    """
    def handleLogResponse(self,retCode,retInfo,extraInfo=None):

        self.log.debug("handleLogResponse =%d,%s"%(retCode,retInfo))
        if(retCode==200):

           self.status = ES_IDLE


    def handleLogOutResponse(self,retCode,retInfo,extraInfo=None):

        self.log.debug("handleLogOutResponse =%d,%s"%(retCode,retInfo))
        self.status = ES_UNLOGGED
        self.msgProxy.handle_close(self.mainSession)

    def handlePublishResponse(self,retCode,retInfo,extraInfo=None):

        self.log.debug("handlePublishResponse =%d,%s"%(retCode,retInfo))
        if(retCode==200):
            for service in self.serviceList:
                if service.cseq == extraInfo[CSTR2_CSEQ]:
                    service.pubID = extraInfo[CSTR2_PUBID]
                    service.updateStatus(ESE_WF_ORDER)
                    service.published = True
                    break
    def handleUnPublishResponse(self,retCode,retInfo,extraInfo=None):

        self.log.debug("handleUnPublishResponse =%d,%s"%(retCode,retInfo))
       

    
        
    """
    handle request
    """
    def handleRequest(self,session,method,params):
        return self.requestMap[method](session,params)

    def handleNotifyRequest(self,session,params):
        action = params[0]
        #self.log.debug("before process notify:%s"%action)
        return self.notifyRequestMap[action](session,params[1:])

    def _handleStandbyRequest(self,name,session,params,timeout,reporter1=None):

        task = StandbyTask(self,session,params,name,timeout,reporter=reporter1)
        self.taskEngine.addTask(task,session)
    """
    handle response
    """
    def handleResponse(self,method,retCode,retInfo,extraInfo=None):
        #return self.responseMap[method](retCode,retInfo,extraInfo)
        raise Exception,"not used so far,all response are processd by reporter"

    def changeSeriveStatusByResponse(self,pubID,retCode):
        """
        This is used,in common, for all the notify response from peer, to change the status is 
        """
        
        curService = self.queryService(pubID)
        self.log.debug("cur=%s,(%s,%s)"%(FSMTable[curService.status][2],FSMTable[FSMTable[curService.status][0]][2],FSMTable[FSMTable[curService.status][1]][2]))
        if retCode==EN_OK:
            curService.updateStatus(FSMTable[curService.status][0])
        else:
            curService.updateStatus(FSMTable[curService.status][1])
            
        


    """

    serive related operations
    """
    def addService(self,reporter,paramsDict):
        """
        return the pubID
        """
        newService = TaxiService(self,paramsDict)
        self.serviceList.append(newService)
        self._publish(newService,reporter)
        return newService

    def modifyService(self,service,args):
        """
        Not used so far
        TODO: delete cur service and create a new service


        if(args.has_key("departureLocation")):
            service.departureLocation = args["departureLocation"]
        if(args.has_key("departureTime")):
            service.departureLocation = args["departureTime"]
        if(args.has_key("timeout")):
            service.departureLocation = args["timeout"]
        """

    def queryService(self,id):

        for service in self.serviceList:
            if service.pubID == id:
                return service
        return None
    
    def queryServiceByStatus(self,status):
    
        return [service for service in self.serviceList if service.status==status]
    
    def deleteService(self,reporter,service):
        
        if service.status==ESE_WF_ORDER:# and service.published:
            self._unPublish(service,reporter)
        if service in self.serviceList:
            self.serviceList.remove(service)
        if service.session:
            self.msgProxy.handle_close(service.session)
    """
    utility
    """
    def getNextSessionID(self):
        self.sessionID += 1
        return self.sessionID

    def getMsgEngine(self):
        return self.msgEngine

    def getTaskEngine(self):
        return self.taskEngine

    def getLocation(self):
        return self.owner.getLocation()
    
    #def _handleGotLocation(self,paramDict):     
def test(dict1):
    dict1[CSTR3_RESULT].append(1)
            
if __name__=="__main__":

     result = {1:"sdf",2:"dsfsdf"}
     paramDict={CSTR3_RESULT:3}
     for key,value in result.items():
               paramDict[key] = value
     print paramDict